/*
 * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

@file:OptIn(DeprecatedTargetPresetApi::class, InternalKotlinGradlePluginApi::class)
@file:Suppress("DEPRECATION")

package org.jetbrains.kotlin.generators.gradle.dsl

import org.gradle.api.Action
import org.jetbrains.kotlin.gradle.DeprecatedTargetPresetApi
import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetsContainerWithPresets
import java.io.File

fun main() {
    generateKotlinTargetContainerWithPresetFunctionsInterface()
}

private val parentInterface = KotlinTargetsContainerWithPresets::class

private val presetsProperty = KotlinTargetsContainerWithPresets::presets.name

private fun generateKotlinTargetContainerWithPresetFunctionsInterface() {
    // Generate KotlinMultiplatformExtension subclass with member functions for the presets:
    val functions = allPresetEntries.map { kotlinPreset ->
        generatePresetFunctions(kotlinPreset, presetsProperty, "configureOrCreate")
    }

    val parentInterfaceName =
        typeName(parentInterface.java.canonicalName)

    val className =
        typeName("org.jetbrains.kotlin.gradle.dsl.KotlinTargetContainerWithPresetFunctions")

    val deprecatedMessageVal = typeName("org.jetbrains.kotlin.konan.target.DEPRECATED_TARGET_MESSAGE")

    val imports = allPresetEntries
        .flatMap { it.typeNames() }
        .plus(parentInterfaceName)
        .plus(deprecatedMessageVal)
        .plus(typeName(Action::class.java.canonicalName))
        .filter { it.packageName() != className.packageName() }
        .flatMap { it.collectFqNames() }
        .toSortedSet()
        .joinToString("\n") { "import $it" }

    val generatedCodeWarning = "// DO NOT EDIT MANUALLY! Generated by ${object {}.javaClass.enclosingClass.name}"

    val extraTopLevelDeclarations = allPresetEntries.flatMap { it.extraTopLevelDeclarations }.joinToString("\n")

    val code = listOf(
        "package ${className.packageName()}",
        imports,
        generatedCodeWarning,
        extraTopLevelDeclarations,
        "@KotlinGradlePluginDsl\ninterface ${className.renderShort()} : ${parentInterfaceName.renderShort()} {",
        functions.joinToString("\n\n") { it.indented(4) },
        "}"
    ).joinToString("\n\n")

    val targetFile = File("$kotlinGradlePluginSourceRoot/${className.fqName.replace(".", "/")}.kt")
    targetFile.writeText(code)
}

private fun generatePresetFunctions(
    presetEntry: KotlinPresetEntry,
    getPresetsExpression: String,
    configureOrCreateFunctionName: String,
): String {
    fun deprecated(replaceWithArguments: List<String>? = null): String {
        val deprecation = presetEntry.deprecation ?: return ""

        val deprecationAnnotation = if (deprecation.replaceWithOtherPreset != null && replaceWithArguments != null) {
            val replaceWith = "ReplaceWith(\"${deprecation.replaceWithOtherPreset}(${replaceWithArguments.joinToString(",")})\")"
            "@Deprecated(${deprecation.message}, level = DeprecationLevel.${deprecation.level.name}, replaceWith = $replaceWith)"
        } else {
            "@Deprecated(${deprecation.message}, level = DeprecationLevel.${deprecation.level.name})"
        }

        // magic indent is needed to make the result look pretty
        return "\n    $deprecationAnnotation\n    "
    }

    val suppress = if (presetEntry.deprecation != null) {
        val suppressDeprecationId = when (presetEntry.deprecation.level) {
            DeprecationLevel.WARNING -> "DEPRECATION"
            DeprecationLevel.ERROR -> "DEPRECATION_ERROR"
            DeprecationLevel.HIDDEN -> "DEPRECATION_HIDDEN"
        }
        "@Suppress(\"$suppressDeprecationId\")\n    "
    } else {
        ""
    }

    // Suppress presets deprecation to prevent warnings inside kotlin-gradle-plugin
    val suppressPresetsDeprecation = "@Suppress(\"DEPRECATION\")"

    val alsoBlockAfterConfiguration = if (presetEntry.alsoBlockAfterConfiguration != null) {
        """
            .also {
                ${presetEntry.alsoBlockAfterConfiguration.indented(16, skipFirstLine = true)}
            }
        """.trimIndent().indented(8, skipFirstLine = true)
    } else {
        ""
    }

    val presetName = presetEntry.presetName
    val entityName = presetEntry.entityName

    return """
    ${deprecated()}fun $presetName(
        name: String = "$entityName",
        configure: ${presetEntry.targetType.renderShort()}.() -> Unit = { }
    ): ${presetEntry.targetType.renderShort()} =
        $configureOrCreateFunctionName(
            name,
            ${suppressPresetsDeprecation}
            $getPresetsExpression.getByName("$entityName") as ${presetEntry.presetType.renderShort()},
            configure
        )$alsoBlockAfterConfiguration

    ${deprecated(emptyList())}${suppress}fun $presetName() = $presetName("$entityName") { }
    ${deprecated(listOf("name"))}${suppress}fun $presetName(name: String) = $presetName(name) { }
    ${deprecated()}${suppress}fun $presetName(name: String, configure: Action<${presetEntry.targetType.renderShort()}>) = $presetName(name) { configure.execute(this) }
    ${deprecated()}${suppress}fun $presetName(configure: Action<${presetEntry.targetType.renderShort()}>) = $presetName { configure.execute(this) }
""".trimIndent()
}
